#include "Heightmap.h"

Heightmap::Heightmap() {
    h = 0;
    w = 0;
    data = 0;
}

Heightmap::Heightmap(FIBITMAP* source, float scale) {
    setData(source, scale);
}

Heightmap::~Heightmap() {
    delete[] data;
}

void Heightmap::setData(FIBITMAP* source, float scale) {
    h = FreeImage_GetHeight(source);
    w = FreeImage_GetWidth(source);

    data = new float[h*w];
    
    for (int i = 0; i < h; i++)
        for (int j = 0; j < w; j++) {
            RGBQUAD rawColor;
            FreeImage_GetPixelColor(source, j, i, &rawColor);
            at(j, i) = (rawColor.rgbRed / 255.0f + rawColor.rgbGreen / 255.0f + rawColor.rgbBlue / 255.0f) * scale / 3.0f;
        }
}

void Heightmap::setData(Heightmap& heightmap) {
    h = heightmap.height();
    w = heightmap.width();

    data = new float[h*w];
    
    for (int i = 0; i < h; i++)
        for (int j = 0; j < w; j++) {
            at(j, i) = heightmap.at(j, i);
        }
}

void Heightmap::deleteData() {
    delete[] data;
    data = 0;
}

float& Heightmap::at(int x, int y) const {
    return data[y*w+x];
}

float Heightmap::getMaxHeightX(int x, int minY, int maxY) const {
    float result = -FLT_MAX;
    for (int i = minY; i < maxY; i++)
            result = max(result, at(x, i));

    return result;
}

float Heightmap::getMinHeightX(int x, int minY, int maxY) const {
    float result = FLT_MAX;
    for (int i = minY; i < maxY; i++)
        result = min(result, at(x, i));

    return result;
}

float Heightmap::getMaxHeightY(int y, int minX, int maxX) const {
    float result = -FLT_MAX;
    for (int i = minX; i < maxX; i++)
        result = max(result, at(i, y));

    return result;
}

float Heightmap::getMinHeightY(int y, int minX, int maxX) const {
    float result = FLT_MAX;
    for (int i = minX; i < maxX; i++)
        result = min(result, at(i, y));

    return result;
}

void Heightmap::getOptSplitX(float& score, int& split, int minX, int maxX, int minY, int maxY) const {
    int length = maxX - minX;
    int width = maxY - minY;
    float* maxHeightPos = new float[length - 2];
    float* minHeightPos = new float[length - 2];

    float* maxHeightNeg = new float[length - 2];
    float* minHeightNeg = new float[length - 2];

    float maxHeight;
    float minHeight;

    maxHeight = getMaxHeightX(minX, minY, maxY);
    minHeight = getMinHeightX(minX, minY, maxY);
    for (int i = 1; i < length - 1; i++) {
        int x = minX + i;
        float currMaxHeight = getMaxHeightX(x, minY, maxY);
        float currMinHeight = getMinHeightX(x, minY, maxY);
        maxHeight = max(maxHeight, currMaxHeight);
        minHeight = min(minHeight, currMinHeight);
        maxHeightPos[i-1] = maxHeight;
        minHeightPos[i-1] = minHeight;
    }

    maxHeight = getMaxHeightX(maxX - 1, minY, maxY);
    minHeight = getMinHeightX(maxX - 1, minY, maxY);
    for (int i = length - 2; i >= 1; i--) {
        int x = minX + i;
        float currMaxHeight = getMaxHeightX(x, minY, maxY);
        float currMinHeight = getMinHeightX(x, minY, maxY);
        maxHeight = max(maxHeight, currMaxHeight);
        minHeight = min(minHeight, currMinHeight);
        maxHeightNeg[i-1] = maxHeight;
        minHeightNeg[i-1] = minHeight;
    }

    score = FLT_MAX;

    for (int i = 1; i < length - 1; i++) {
        int x = minX + i;
        float heightDiffPos = (maxHeightPos[i-1] - minHeightPos[i-1]);
        float heightDiffNeg = (maxHeightNeg[i-1] - minHeightNeg[i-1]);
        float perimPos = i + width;
        float perimNeg = length - 1 - i + width;
        float areaPos = i * width;
        float areaNeg = (length - 1 - i) * width;
        float surfacePos = heightDiffPos * perimPos + areaPos;
        float surfaceNeg = heightDiffNeg * perimNeg + areaNeg;
        float currScore = surfacePos * areaPos + surfaceNeg * areaNeg;
        if (currScore < score) {
            score = currScore;
            split = x;
        }
    }
}

void Heightmap::getOptSplitY(float& score, int& split, int minX, int maxX, int minY, int maxY) const {
    int length = maxY - minY;
    int width = maxX - minX;
    float* maxHeightPos = new float[length - 2];
    float* minHeightPos = new float[length - 2];

    float* maxHeightNeg = new float[length - 2];
    float* minHeightNeg = new float[length - 2];

    float maxHeight;
    float minHeight;

    maxHeight = getMaxHeightY(minY, minX, maxX);
    minHeight = getMinHeightY(minY, minX, maxX);
    for (int i = 1; i < length - 1; i++) {
        int y = minY + i;
        float currMaxHeight = getMaxHeightY(y, minX, maxX);
        float currMinHeight = getMinHeightY(y, minX, maxX);
        maxHeight = max(maxHeight, currMaxHeight);
        minHeight = min(minHeight, currMinHeight);
        maxHeightPos[i-1] = maxHeight;
        minHeightPos[i-1] = minHeight;
    }

    maxHeight = getMaxHeightY(maxY - 1, minX, maxX);
    minHeight = getMinHeightY(maxY - 1, minX, maxX);
    for (int i = length - 2; i >= 1; i--) {
        int y = minY + i;
        float currMaxHeight = getMaxHeightY(y, minX, maxX);
        float currMinHeight = getMinHeightY(y, minX, maxX);
        maxHeight = max(maxHeight, currMaxHeight);
        minHeight = min(minHeight, currMinHeight);
        maxHeightNeg[i-1] = maxHeight;
        minHeightNeg[i-1] = minHeight;
    }

    score = FLT_MAX;

    for (int i = 1; i < length - 1; i++) {
        int y = minY + i;
        float heightDiffPos = (maxHeightPos[i-1] - minHeightPos[i-1]);
        float heightDiffNeg = (maxHeightNeg[i-1] - minHeightNeg[i-1]);
        float perimPos = i + width;
        float perimNeg = length - 1 - i + width;
        float areaPos = i * width;
        float areaNeg = (length - 1 - i) * width;
        float surfacePos = heightDiffPos * perimPos + areaPos;
        float surfaceNeg = heightDiffNeg * perimNeg + areaNeg;
        float currScore = surfacePos * areaPos + surfaceNeg * areaNeg;
        if (currScore < score) {
            score = currScore;
            split = y;
        }
    }
}
